// 12.1 动态内存与智能指针
/**
 * 在C++中，动态内存的管理是通过一对运算符来完成的：
 * 1. new，在动态内存中为对象分配空间并返回一个指向该对象的指针，我们可以选择对对象进行初始化；
 * 2. delete，接受一个动态对象的指针，销毁该对象，并释放与之关联的内存。
 * 新标准库提供的这两种智能指针的区别在于管理底层指针的方式：
 * 1. shared_ptr允许多个指针指向同一个对象；
 * 2. unique_ptr则“独占”所指向的对象。
 * 标准库还定义了一个名为weak_ptr的伴随类，它是一种弱引用，指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
using namespace std;

shared_ptr<Sales_data> factory(string arg)
{
    // 处理args
    return make_shared<Sales_data>(arg); // shared_ptr负责释放内存
}

void use_factory(string arg)
{
    shared_ptr<Sales_data> p = factory(arg);
    // 使用p
} // 离开作用域后，它指向的内存会被自动释放

shared_ptr<Sales_data> use_factory1(string arg)
{
    shared_ptr<Sales_data> p = factory(arg);
    // 使用p
    return p; // 当我们返回p时，引用计数进行了递增操作
} // 离开作用域后，但它指向的内存不会被释放掉

// 对于StrBlob中的友元声明来说，此前置声明是必要的
class StrBlobPtr;
class StrBlob
{
    friend class StrBlobPtr; // 友元类
public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);           // 此构造函数可以接受一个初始化器的花括号列表
    size_type size() const { return data->size(); } // 常量成员函数
    bool empty() const { return data->empty(); }
    // 添加和删除元素
    void push_back(const string &t) { data->push_back(t); }
    void pop_back();
    // 元素访问
    string &front();
    string &back();
    // 返回指向首元素和尾后元素的StrBlobPtr
    //StrBlobPtr begin() { return StrBlobPtr(*this); }
    //StrBlobPtr end() { auto ret = StrBlobPtr(*this, data->size()); return ret; }

private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string &msg) const; // 如果data[i]不合法，抛出一个异常
};
// StrBlob的构造函数
StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
// StrBlob的元素访问成员函数
void StrBlob::check(size_type i, const string &msg) const
{
    if (i >= data->size())
        throw out_of_range(msg);
}
string& StrBlob::front()
{
    // 如果vector为空，check会抛出一个异常
    check(0,"front on empty StrBlob");
    return data->front();
}
string& StrBlob::back()
{
    // 如果vector为空，check会抛出一个异常
    check(0,"back on empty StrBlob");
    return data->back();
}
void StrBlob::pop_back()
{
     check(0,"pop_back on empty StrBlob");
     data->pop_back();
}

int main()
{
    // 12.1.1 shared_ptr类
    // 类似vector，智能指针也是模板（参见3.3节，第86页）。因此，当我们创建一个智能指针时，必须提供额外的信息——指针可以指向的类型。
    shared_ptr<string> p1;    // shared_ptr可以指向string
    shared_ptr<list<int>> p2; // shared_ptr可以指向int的list
    // 默认初始化的智能指针中保存着一个空指针（参见2.3.2节，第48页）。
    // 如果在一个条件判断中使用智能指针，效果就是检测它是否为空：[插图]
    if (p1 && p1->empty()) // 如果p1不为空，检查它是否指向一个空string
    {
        *p1 = "hi"; // 如果p1指向一个空string，解引用p1，赋予hi
    }
    // shared_ptr和unique_ptr都支持的操作
    // 1. shared_ptr<T> sp 空智能指针，可以指向T类型的对象
    // 2. unique_ptr<T> up
    // 3. p 将p用作条件判断，若p指向一个对象，则p为true
    // 4. *p 解引用p，获得它指向的对象
    // 5. p->mem 等价于(*p).mem
    // 6. p.get() 返回p中保存的指针。要小心使用，若智能指针释放了其对象，返回的指针所指向的对象也就消失了
    // 7. swap(p,q) 交换p和q的指针
    // 8. p.swap(q)
    // shared_ptr独有的操作
    // 1. make_shared<T>(args) 返回一个shared_ptr，指向一个动态分配的类型为T的对象。使用args初始化此对象
    // 2. shared_ptr<T> p(q) p是q的拷贝。此操作会递增q中的计数器，q中的指针必须能转换为T*
    // 3. p = q p和q都是shared_ptr，所保存的指针必须能互相转换。此操作会递减p的引用计数，递增q的引用计数。若q的引用计数变为0，则将其管理的原内存释放
    // 4. p.unique() 若p.use_count()为1，返回true，否则false
    // 5. p.use_count() 返回与p共享对象的智能指针数量，可能很慢，主要用于调试

    // make_shared函数
    // 最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。
    // 此函数在动态内存中分配一个对象并初始化它，返回指向此对象的shared_ptr。
    shared_ptr<int> p3 = make_shared<int>(42);            // 指向一个值为42的int的shared_ptr
    shared_ptr<string> p4 = make_shared<string>(10, '9'); // p4指向一个值为'9999999999'的string
    shared_ptr<int> p5 = make_shared<int>();              // p5指向一个值初始化的int，值为0
    auto p6 = make_shared<vector<string>>();              // p6指向一个动态分配的空vector<string>

    // shared_ptr的拷贝和赋值
    // 当进行拷贝或赋值操作时，每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象：[插图]
    auto p = make_shared<int>(42); // p指向的对象只有p一个引用者
    auto q(p);                     // p和q指向相同对象，此对象有两个引用者
    // 我们可以认为每个shared_ptr都有一个关联的计数器，通常称其为引用计数（reference count）。
    // 无论何时我们拷贝一个shared_ptr，计数器都会递增。
    // 当我们给shared_ptr赋予一个新值或是shared_ptr被销毁（例如一个局部的shared_ptr离开其作用域（参见6.1.1节，第184页））时，
    // 计数器就会递减。
    // 一旦一个shared_ptr的计数器变为0，它就会自动释放自己所管理的对象：[插图]
    auto r = make_shared<int>(42); // r指向的对象只有r一个引用者
    r = q;                         // 给r赋值，令它指向另一个地址
                                   // 递增q指向的对象的引用计数
                                   // 递减r原来指向的对象的引用计数
                                   // r原来指向的对象已没有引用者，会自动释放

    // shared_ptr自动销毁所管理的对象
    // 当指向一个对象的最后一个shared_ptr被销毁时，shared_ptr类会自动销毁此对象。
    // 它是通过另一个特殊的成员函数——析构函数（destructor）完成销毁工作的。
    // 析构函数一般用来释放对象所分配的资源。
    // shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0，shared_ptr的析构函数就会销毁对象，并释放它占用的内存。

    // shared_ptr还会自动释放相关联的内存
    use_factory("1111");
    // 但如果有其他shared_ptr也指向这块内存，它就不会被释放掉：[插图]
    use_factory1("2222");
    // 拷贝一个shared_ptr会增加所管理对象的引用计数值。
    // 由于在最后一个shared_ptr销毁前内存都不会释放，保证shared_ptr在无用之后不再保留就非常重要了。
    // 如果你忘记了销毁程序不再需要的shared_ptr，程序仍会正确执行，但会浪费内存。
    // 如果你将shared_ptr存放于一个容器中，而后不再需要全部元素，而只使用其中一部分，要记得用erase删除不再需要的那些元素。

    // 使用了动态生存期的资源的类
    // 程序使用动态内存出于以下三种原因之一：
    // 1. 程序不知道自己需要使用多少对象（例如容器类）
    // 2. 程序不知道所需对象的准确类型（第15章面向对象程序设计中可以看到）
    // 3. 程序需要在多个对象间共享数据
    // 当我们拷贝一个vector时，原vector和副本vector中的元素是相互分离的：
    vector<string> v1; // 空vector
    {                  // 新作用域
        vector<string> v2 = {"a", "an", "the"};
        v2 = v2; // 从v2拷贝元素到v1
    }            // 离开作用域，v2被销毁，其中的元素也被销毁
    // v1有3个元素，是原来v2中元素的拷贝
    // 一般而言，如果两个对象共享底层的数据，当某个对象被销毁时，我们不能单方面地销毁底层数据：[插图]
    // 假设Blob对象的不同拷贝之间共享相同的元素，即当我们拷贝一个Blob时，原Blob对象及其拷贝应该引用相同的底层元素：
    // Blob<string> b1; // 空Blob
    // {                // 新作用域
    //     Blob<string> b2 = {"a", "an", "the"};
    //     b1 = b2; // b1和b2共享相同的元素
    // }            // b2被销毁了，但b2中的元素不能销毁
    // b1指向最初由b2创建的元素
    // 使用动态内存的一个常见原因是允许多个对象共享相同的状态。
    // 最终，我们会将Blob类实现为一个模板，但我们直到16.1.2节（第583页）才会学习模板的相关知识。
    // 因此，现在我们先定义一个管理string的类，此版本命名为StrBlob。

    return 0;
}